package com.javaeye.dengyin2000.gwtmodel.client.utils;


import java.util.List;
import java.util.Map;

import pl.rmalinowski.gwtreflect.client.reflect.Class;
import pl.rmalinowski.gwtreflect.client.reflect.ClassNotFoundException;
import pl.rmalinowski.gwtreflect.client.reflect.GWTRClassNotFoundException;
import pl.rmalinowski.gwtreflect.client.reflect.GWTRUtil;
import pl.rmalinowski.gwtreflect.client.reflect.IllegalAccessException;
import pl.rmalinowski.gwtreflect.client.reflect.IllegalArgumentException;
import pl.rmalinowski.gwtreflect.client.reflect.InvocationTargetException;
import pl.rmalinowski.gwtreflect.client.reflect.Method;
import pl.rmalinowski.gwtreflect.client.reflect.NoSuchMethodException;

import com.google.gwt.core.client.GWT;



/**
 * 
 * @author 邓胤 deng.yin@gmail.com http://dengyin2000.javaeye.com
 * @version 0.1beta
 *
 */
public class PropertyUtils {

    /**
     * The delimiter that preceeds the zero-relative subscript for an
     * indexed reference.
     */
    public static final char INDEXED_DELIM = '[';


    /**
     * The delimiter that follows the zero-relative subscript for an
     * indexed reference.
     */
    public static final char INDEXED_DELIM2 = ']';


    /**
     * The delimiter that preceeds the key of a mapped property.
     */
    public static final char MAPPED_DELIM = '(';


    /**
     * The delimiter that follows the key of a mapped property.
     */
    public static final char MAPPED_DELIM2 = ')';


    /**
     * The delimiter that separates the components of a nested reference.
     */
    public static final char NESTED_DELIM = '.';
    
	public static void setProperty(Object bean, String name,
			Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified");
        }

        int indexOfINDEXED_DELIM = -1;
        int indexOfMAPPED_DELIM = -1;
        while (true) {
            int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
            if (delim < 0) {
                break;
            }
            String next = name.substring(0, delim);
            indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
            indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
            if (bean instanceof Map) {
                bean = ((Map) bean).get(next);
            } else if (indexOfMAPPED_DELIM >= 0) {
                bean = getMappedProperty(bean, next);
            } else if (indexOfINDEXED_DELIM >= 0) {
                bean = getIndexedProperty(bean, next);
            } else {
                bean = getSimpleProperty(bean, next);
            }
            if (bean == null) {
                throw new IllegalArgumentException
                        ("Null property value for '" +
                        name.substring(0, delim) + "'");
            }
            name = name.substring(delim + 1);
        }

        indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
        indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);

        if (bean instanceof Map) {
                // no - then put the value into the map
        	((Map) bean).put(name, value);

        } else if (indexOfMAPPED_DELIM >= 0) {
            setMappedProperty(bean, name, value);
        } else if (indexOfINDEXED_DELIM >= 0) {
            setIndexedProperty(bean, name, value);
        } else {
            setSimpleProperty(bean, name, value);
        }

    }
	
    private static void setSimpleProperty(Object bean, String name, Object value)
			throws IllegalAccessException, InvocationTargetException,
			NoSuchMethodException,  ClassNotFoundException {

		if (bean == null) {
			throw new IllegalArgumentException("No bean specified");
		}
		if (name == null) {
			throw new IllegalArgumentException("No name specified");
		}

		// Validate the syntax of the property name
		if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
			throw new IllegalArgumentException(
					"Nested property names are not allowed");
		} else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
			throw new IllegalArgumentException(
					"Indexed property names are not allowed");
		} else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
			throw new IllegalArgumentException(
					"Mapped property names are not allowed");
		}
		
		Method setterMethod = getSetterMethod(bean, name, value);
		invokeMethod(setterMethod, bean, new Object[]{value});
	}	
	
    private static void setIndexedProperty(Object bean, String name, Object value)
			throws IllegalAccessException, InvocationTargetException,
			NoSuchMethodException, ClassNotFoundException {

		if (bean == null) {
			throw new IllegalArgumentException("No bean specified");
		}
		if (name == null) {
			throw new IllegalArgumentException("No name specified");
		}

		// Identify the index of the requested individual property
		int delim = name.indexOf(PropertyUtils.INDEXED_DELIM);
		int delim2 = name.indexOf(PropertyUtils.INDEXED_DELIM2);
		if ((delim < 0) || (delim2 <= delim)) {
			throw new IllegalArgumentException("Invalid indexed property '"
					+ name + "'");
		}
		int index = -1;
		try {
			String subscript = name.substring(delim + 1, delim2);
			index = Integer.parseInt(subscript);
		} catch (NumberFormatException e) {
			throw new IllegalArgumentException("Invalid indexed property '"
					+ name + "'");
		}
		name = name.substring(0, delim);

		// Set the specified indexed property value
		setIndexedProperty(bean, name, index, value);

	}	
	
    private static void setIndexedProperty(Object bean, String name, int index,
			Object value) throws IllegalAccessException,
			InvocationTargetException, NoSuchMethodException, ClassNotFoundException {

		if (bean == null) {
			throw new IllegalArgumentException("No bean specified");
		}
		if (name == null) {
			throw new IllegalArgumentException("No name specified");
		}

	
		Method getterMethod = getGetterMethod(bean, name);
		Object object = invokeMethod(getterMethod, bean, null);
		if (object instanceof List){
			((List)object).set(index, value);
			return ;
		}
		
		((Object[])object)[index] = value;

	}
    
    private static void setMappedProperty(Object bean, String name, Object value)
			throws IllegalAccessException, InvocationTargetException,
			NoSuchMethodException, ClassNotFoundException {

		if (bean == null) {
			throw new IllegalArgumentException("No bean specified");
		}
		if (name == null) {
			throw new IllegalArgumentException("No name specified");
		}

		// Identify the index of the requested individual property
		int delim = name.indexOf(PropertyUtils.MAPPED_DELIM);
		int delim2 = name.indexOf(PropertyUtils.MAPPED_DELIM2);
		if ((delim < 0) || (delim2 <= delim)) {
			throw new IllegalArgumentException("Invalid mapped property '"
					+ name + "'");
		}

		// Isolate the name and the key
		String key = name.substring(delim + 1, delim2);
		name = name.substring(0, delim);

		// Request the specified indexed property value
		setMappedProperty(bean, name, key, value);

	}
    private static void setMappedProperty(Object bean, String name, String key,
			Object value) throws IllegalAccessException,
			InvocationTargetException, NoSuchMethodException, ClassNotFoundException {

		if (bean == null) {
			throw new IllegalArgumentException("No bean specified");
		}
		if (name == null) {
			throw new IllegalArgumentException("No name specified");
		}
		if (key == null) {
			throw new IllegalArgumentException("No key specified");
		}



        
        Method getterMethod = getGetterMethod(bean, name);
        Object invokeResult = invokeMethod(getterMethod, bean, null);
        if (invokeResult instanceof java.util.Map){
        	((java.util.Map)invokeResult).put(key, value);
        }
	}    

	public static Object getProperty(Object bean, String name)throws IllegalAccessException, InvocationTargetException,
    NoSuchMethodException, IllegalArgumentException, NullPointerException, ClassNotFoundException  {
        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified");
        }
        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified");
        }

        int indexOfINDEXED_DELIM = -1;
        int indexOfMAPPED_DELIM = -1;
        int indexOfMAPPED_DELIM2 = -1;
        int indexOfNESTED_DELIM = -1;
        while (true) {
            indexOfNESTED_DELIM  = name.indexOf(PropertyUtils.NESTED_DELIM);
            indexOfMAPPED_DELIM  = name.indexOf(PropertyUtils.MAPPED_DELIM);
            indexOfMAPPED_DELIM2 = name.indexOf(PropertyUtils.MAPPED_DELIM2);
            if (indexOfMAPPED_DELIM2 >= 0 && indexOfMAPPED_DELIM >=0 &&
                (indexOfNESTED_DELIM < 0 || indexOfNESTED_DELIM > indexOfMAPPED_DELIM)) {
                indexOfNESTED_DELIM =
                    name.indexOf(PropertyUtils.NESTED_DELIM, indexOfMAPPED_DELIM2);
            } else {
                indexOfNESTED_DELIM = name.indexOf(PropertyUtils.NESTED_DELIM);
            }
            if (indexOfNESTED_DELIM < 0) {
                break;
            }
            String next = name.substring(0, indexOfNESTED_DELIM);
            indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
            indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
            if (bean instanceof Map) {
                bean = ((Map) bean).get(next);
            } else if (indexOfMAPPED_DELIM >= 0) {
                bean = getMappedProperty(bean, next);
            } else if (indexOfINDEXED_DELIM >= 0) {
                bean = getIndexedProperty(bean, next);
            } else {
                bean = getSimpleProperty(bean, next);
            }
            if (bean == null) {
                throw new NoSuchMethodException
                        ("Null property value for '" +
                        name.substring(0, indexOfNESTED_DELIM) + "'");
            }
            name = name.substring(indexOfNESTED_DELIM + 1);
        }

        indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
        indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);

        if (bean instanceof Map) {
            bean = ((Map) bean).get(name);
        } else if (indexOfMAPPED_DELIM >= 0) {
            bean = getMappedProperty(bean, name);
        } else if (indexOfINDEXED_DELIM >= 0) {
            bean = getIndexedProperty(bean, name);
        } else {
            bean = getSimpleProperty(bean, name);
        }
        return bean;
	}

	private static Object getSimpleProperty(Object bean, String name) throws ClassNotFoundException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, NullPointerException, InvocationTargetException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified");
        }

        // Validate the syntax of the property name
        if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
            throw new IllegalArgumentException
                    ("Nested property names are not allowed");
        } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
            throw new IllegalArgumentException
                    ("Indexed property names are not allowed");
        } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
            throw new IllegalArgumentException
                    ("Mapped property names are not allowed");
        }

        Method getterMethod = getGetterMethod(bean, name);
        Object value = invokeMethod(getterMethod, bean, null);
        return value;
    }

	private static Object getIndexedProperty(Object bean, String name) throws IllegalArgumentException, IllegalAccessException, NullPointerException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified");
        }

        // Identify the index of the requested individual property
        int delim = name.indexOf(PropertyUtils.INDEXED_DELIM);
        int delim2 = name.indexOf(PropertyUtils.INDEXED_DELIM2);
        if ((delim < 0) || (delim2 <= delim)) {
            throw new IllegalArgumentException("Invalid indexed property '" +
                    name + "'");
        }
        int index = -1;
        try {
            String subscript = name.substring(delim + 1, delim2);
            index = Integer.parseInt(subscript);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid indexed property '" +
                    name + "'");
        }
        name = name.substring(0, delim);

        // Request the specified indexed property value
        return (getIndexedProperty(bean, name, index));

    }

	private static Object getIndexedProperty(Object bean, String name, int index) throws ClassNotFoundException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, NullPointerException, InvocationTargetException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified");
        }
        
        Method getterMethod = getGetterMethod(bean, name);
        Object object = invokeMethod(getterMethod, bean, null);


        if(object instanceof List){
        	return ((List)object).get(index);
        }
        
        return ((Object[])object)[index];
	}

	private static Object getMappedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException, ClassNotFoundException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified");
        }

        // Identify the index of the requested individual property
        int delim = name.indexOf(PropertyUtils.MAPPED_DELIM);
        int delim2 = name.indexOf(PropertyUtils.MAPPED_DELIM2);
        if ((delim < 0) || (delim2 <= delim)) {
            throw new IllegalArgumentException
                    ("Invalid mapped property '" + name + "'");
        }

        // Isolate the name and the key
        String key = name.substring(delim + 1, delim2);
        name = name.substring(0, delim);

        // Request the specified indexed property value
        return (getMappedProperty(bean, name, key));

    }

	private static Object getMappedProperty(Object bean,
            String name, String key) throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException, ClassNotFoundException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified");
        }
        if (key == null) {
            throw new IllegalArgumentException("No key specified");
        }



        Object result = null;
        
        Method getterMethod = getGetterMethod(bean, name);
        Object invokeResult = invokeMethod(getterMethod, bean, null);
        if (invokeResult instanceof java.util.Map){
        	  result = ((java.util.Map)invokeResult).get(key);
        }
        return result;
    }
	
	private  static Method getGetterMethod(Object object, String name) throws ClassNotFoundException, NoSuchMethodException{
		Class clazz = Class.getClass(GWT.getTypeName(object));
		String realName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
		Method method = clazz.getMethod(realName, new java.lang.Class[] {});
		return method;
	}
	
	private static Method getSetterMethod(Object object, String name, Object value) throws ClassNotFoundException, NoSuchMethodException{
		Class clazz = Class.getClass(GWT.getTypeName(object));
		String realName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
		Method method = null;
		try{
			method = clazz.getMethod(realName, new Class[]{GWTRUtil.getClass(GWT.getTypeName(value))});
		}catch(GWTRClassNotFoundException e){
			throw new NoSuchMethodException(e);
		}
		return method;
	}
	
	private static Object invokeMethod(Method method, Object bean, Object[] params) throws IllegalArgumentException, IllegalAccessException, NullPointerException, InvocationTargetException{
		return method.invoke(bean, params);
	}
}
